package mop.xml;

import java.io.File;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import mop.exception.IllegalIdException;

import mop.model.World;
import mop.model.project.Project;
import mop.model.resource.Resource;
import mop.model.task.Task;
import mop.model.user.User;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * XML reader (SAX) for the MOP project.
 * 
 * @author 	MOP Team 7
 */
public class TaskplannerXMLReader extends DefaultHandler {

    public static void main(String args[]) {
        World world = new World();
        ThemeXMLReader themeHandler = new ThemeXMLReader(world);
        File xmlFile = new File("theme_development_1.xml");
        themeHandler.process(xmlFile);
        TaskplannerXMLReader taskplannerHandler = new TaskplannerXMLReader(world);
        xmlFile = new File("students_public_3.xml");
        taskplannerHandler.process(xmlFile);
        System.out.println("\nAll done!");
    }

    // debug mode on = true
    private final boolean DEBUG_MODE = false;
    // reference to World object
    private World world;
    // parser object
    private SAXParser parser;

    /**
     * Constructor for this SAX parser.
     *
     * Initializes the parser object and the "tags" hashmap
     * containing the booleans indicating which elements are being read.
     */
    public TaskplannerXMLReader(World world) {
        this.world = world;
        this.parser = null;
        this.tags = new HashMap<String, Boolean>();

        this.lastAttr = new HashMap<String, String>();
        this.projects = new LinkedList<ProjectXML>();
        this.resources = new LinkedList<ResourceXML>();
        this.users = new LinkedList<UserXML>();
    }

    /**
     * Starts the processing of an XML file.
     *
     * @param 	file
     * 			An XML file as a File object.
     */
    public void process(File file) {
        SAXParserFactory spf = SAXParserFactory.newInstance();
        spf.setNamespaceAware(true);
        spf.setValidating(true);

        // Make SAX parser

        try {
            parser = spf.newSAXParser();
        } catch (SAXException e) {
            e.printStackTrace(System.err);
            System.exit(1);
        } catch (ParserConfigurationException e) {
            e.printStackTrace(System.err);
            System.exit(1);
        }

        // Start parsing

        try {
            parser.parse(file, this);
        } catch (IOException e) {
            e.printStackTrace(System.err);
        } catch (SAXException e) {
            e.printStackTrace(System.err);
        }

        // Done parsing - fill databases

        // Add projects
        for (ProjectXML px : this.projects) {
            try {
                new Project(world.getProjectDataBase(), px.id, px.description);
            } catch (Throwable th) {
                handleError(th);
            }
        }

        // Add resources (Types must be loaded!!!)
        for (ResourceXML rx : this.resources) {
            try {
                Resource r = new Resource(world.getResourceTypeDataBase().find(rx.type), rx.id, rx.description, world.getResourceDataBase());
            } catch (Throwable t) {
                handleError(t);
            }
        }

        // Set system time after populating the domain
        World.setVirtualTime(systime);
        if (this.DEBUG_MODE) {
            System.out.println("-- VIRTUAL TIME: " + World.getTime());
        }

        // Add users (Types must be loaded!!!)
        HashMap<Task,String> statusses = new HashMap<Task,String>();
        HashMap<String,List<String>> deps = new HashMap<String,List<String>>();
        for (UserXML ux : this.users) {
            try {
                User u = new User(world.getUserTypeDataBase().find(ux.type), ux.id, ux.name, world.getUserDataBase());

                // Add tasks
                for (TaskXML tx : ux.tasks) {
                    // Add task
                    Task t = new Task(tx.id, world.getTaskTypeDataBase().find(tx.type), tx.fields, tx.start, tx.duration, tx.deadline, new LinkedList<Task>(), u);
                    try {
                        t.setProject(world.getProjectDataBase().find(tx.refProject));
                    } catch (Throwable thh) {
                        handleError(thh);
                    }
                    // Add reservations
                    for (ReservationXML rx : tx.reservations) {
                        Resource resource = world.getResourceDataBase().find(rx.refResource);
                        resource.addReservation(t, rx.time, rx.duration);
                    }
                    // Remember stuff
                    statusses.put(t, tx.status);
                    deps.put(tx.id, tx.depTasks);
                }

            } catch (Throwable th) {
                handleError(th);
            }
        }

        // Update statusses
        for (Task t : statusses.keySet())
            t.forceStatus(statusses.get(t));

        // Add dependencies
        for (String s : deps.keySet()) {
            try {
                Task t = getTask(s);
                List<Task> dependencies = new LinkedList<Task>();
                for (String sd : deps.get(s))
                    dependencies.add(getTask(sd));
                t.trySetDependencies(dependencies);
            } catch (Throwable th) {
                handleError(th);
            }
        }
    }

    /**
     * @see		DefaultHandler
     */
    public void startDocument() {
    }

    /**
     * @see		DefaultHandler
     */
    public void endDocument() {
    }
    // Booleans indicating which tag is "active".
    private HashMap<String, Boolean> tags;

    /**
     * Returns whether the given element (String) is active or not.
     *
     * @param 	el
     * 			A string representing an XML element.
     * @return	True if the tag was recently read (and the end tag not yet), else false.
     */
    private boolean isElementActive(String el) {
        return (tags.get(el) != null && tags.get(el));
    }

    /**
     * @see		DefaultHandler
     */
    public void startElement(String uri, String localName, String qname, Attributes attr) {
        tags.put(localName, true);

        // Save attributes
        if (attr != null && attr.getLength() > 0) {
            for (int i = 0; i < attr.getLength(); i++) {
                this.lastAttr.put(attr.getLocalName(i), attr.getValue(i));
            }
        }

        // Project?
        if (localName.equals("project")) {
            ProjectXML p = new ProjectXML(lastAttr.get("id"));
            this.projects.add(p);
        }

        // Resource?
        if (localName.equals("resource")) {
            ResourceXML r = new ResourceXML(lastAttr.get("id"));
            this.resources.add(r);
        }

        // User?
        if (localName.equals("user")) {
            UserXML u = new UserXML(lastAttr.get("id"));
            this.users.add(u);
        }

        // Task?
        if (localName.equals("task")) {
            TaskXML t = new TaskXML(lastAttr.get("id"));
            this.users.get(users.size()-1).tasks.add(t);
        }

        // Field?
        if (localName.equals("field")) {
            UserXML u = users.get(users.size()-1);
            TaskXML t = u.tasks.get(u.tasks.size()-1);
            t.fields.put(lastAttr.get("id"), lastAttr.get("value"));
            if (this.DEBUG_MODE) {
                System.out.println(" FIELD id \"" + lastAttr.get("id") + "\" : " + lastAttr.get("value"));
            }
        }
    }

    /**
     * @see		DefaultHandler
     */
    public void endElement(String uri, String localName, String qname) {
        tags.put(localName, false);
    }

    /**
     * @see		DefaultHandler
     */
    public void characters(char[] ch, int start, int length) {
        String s = new String(ch, start, length).trim();

        // System time?
        if (isElementActive("systemtime")) {
            try {
                this.systime = new Timestamp(TimeParser.parse(s).getTime());
            } catch (Throwable t) {
                handleError(t);
            }
        }

        // Element is project?
        if (isElementActive("project")) {
            ProjectXML p = projects.get(projects.size()-1);
            // Set project description
            if (isElementActive("description")) {
                p.description = s;
                if (this.DEBUG_MODE) {
                    System.out.println("# Project id: " + p.id);
                    System.out.println("Description: " + p.description);
                }
            }
        }

        // Element is resource?
        if (isElementActive("resource")) {
            ResourceXML r = resources.get(resources.size()-1);
            // Set resource description
            if (isElementActive("description")) {
                r.description = s;
                if (this.DEBUG_MODE) {
                    System.out.println("# Resource id: " + r.id);
                    System.out.println("Description: " + r.description);
                }
            }
            // Set resource type
            if (isElementActive("type")) {
                r.type = s;
                if (this.DEBUG_MODE) {
                    System.out.println("Type: " + r.type);
                }
            }
        }

        // Element is user?
        if (isElementActive("user")) {
            UserXML u = users.get(users.size()-1);
            // Set user name
            if (isElementActive("name")) {
                u.name = s;
                if (this.DEBUG_MODE) {
                    System.out.println("# User id: " + u.id);
                    System.out.println("Name: " + u.name);
                }
            }
            // Set user type
            if (isElementActive("type") && !isElementActive("task")) {
                u.type = s;
                if (this.DEBUG_MODE) {
                    System.out.println("UserType: " + u.type);
                }
            }
        }

        // Element is task?
        if (isElementActive("task")) {
            UserXML u = users.get(users.size()-1);
            TaskXML t = u.tasks.get(u.tasks.size()-1);
            // Set task type
            if (isElementActive("type")) {
                t.type = s;
                if (this.DEBUG_MODE) {
                    System.out.println("## Task id: " + t.id);
                    System.out.println(" Type: " + t.type);
                }
            }
            // Set start date
            if (isElementActive("startDate")) {
                try {
                    t.start = new Timestamp(TimeParser.parse(s).getTime());
                } catch (Throwable th) {
                    handleError(th);
                }
                if (this.DEBUG_MODE) {
                    System.out.println(" Start Date: " + t.start);
                }
            }
            // Set end date
            if (isElementActive("endDate")) {
                try {
                    t.deadline = new Timestamp(TimeParser.parse(s).getTime());
                } catch (Throwable th) {
                    handleError(th);
                }
                if (this.DEBUG_MODE) {
                    System.out.println(" End Date: " + t.deadline);
                }
            }
            // Set duration
            if (isElementActive("duration") && !isElementActive("reservation")) {
                t.duration = new Timestamp(Long.parseLong(s) * 60000L);
                if (this.DEBUG_MODE) {
                    System.out.println(" Duration (ms): " + t.duration.getTime());
                }
            }
            // Set status
            if (isElementActive("status")) {
                t.status = s;
                if (this.DEBUG_MODE) {
                    System.out.println(" Status: " + t.status);
                }
            }
            // Set refProject
            if (isElementActive("refProject")) {
                t.refProject = s;
                if (this.DEBUG_MODE) {
                    System.out.println(" RefProject: " + t.refProject);
                }
            }
            // Set depTasks
            if (isElementActive("refTask")) {
                t.depTasks.add(s);
                if (this.DEBUG_MODE) {
                    System.out.println(" RefTask: " + s);
                }
            }
            // Set reservations
            if (isElementActive("reservation")) {
                // Start time?
                if (isElementActive("time")) {
                    try {
                        Timestamp time = new Timestamp(TimeParser.parse(s).getTime());
                        ReservationXML r = new ReservationXML(time);
                        t.reservations.add(r);
                        if (this.DEBUG_MODE) {
                            System.out.println(" - Reservation at: " + time);
                        }
                    } catch (Throwable th) {
                        handleError(th);
                    }
                }
                // Duration?
                if (isElementActive("duration")) {
                    ReservationXML r = t.reservations.get(t.reservations.size()-1);
                    r.duration = new Timestamp(Long.parseLong(s) * 60000L);
                    if (this.DEBUG_MODE) {
                        System.out.println(" Reservation duration (ms): " + r.duration.getTime());
                    }
                }
                // refResource?
                if (isElementActive("refResource")) {
                    ReservationXML r = t.reservations.get(t.reservations.size()-1);
                    r.refResource = s;
                    if (this.DEBUG_MODE) {
                        System.out.println(" Reservation refResource: " + s);
                    }
                }
            }
        }
    }

    /**
     * Find a task that matches a certain id
     *
     * @param 	task
     * 			The task to be converted
     * @throws 	IllegalIdException
     * 			If the given id doesn't exist
     * 			 |getWorld().findTask(id)
     * @return 	The matching task
     */
    private Task getTask(String id)
            throws IllegalIdException
    {
            return world.findTask(id);
    }

    private void handleError(Throwable t) {
        System.out.println("An exception occurred while reading XML!\n" + t.toString());
        t.printStackTrace(System.err);
    }

    // this is all for remembering stuff

    private HashMap<String, String> lastAttr;
    private List<ProjectXML> projects;
    private List<ResourceXML> resources;
    private List<UserXML> users;
    private Timestamp systime;

    private class ProjectXML {
        protected ProjectXML(String id) { this.id = id; }
        protected String id;
        protected String description;
    }
    private class ResourceXML {
        protected ResourceXML(String id) { this.id = id; }
        protected String id;
        protected String description;
        protected String type;
    }
    private class UserXML {
        protected UserXML(String id) { this.id = id; }
        protected String id;
        protected String name;
        protected String type;
        protected List<TaskXML> tasks = new LinkedList<TaskXML>();
    }
    private class TaskXML {
        protected TaskXML(String id) { this.id = id; }
        protected String id;
        protected String type;
        protected Timestamp start;
        protected Timestamp duration;
        protected Timestamp deadline;
        protected String status;
        protected String refProject;
        protected List<String> depTasks = new LinkedList<String>();
        protected HashMap<String,String> fields = new HashMap<String,String>();
        protected List<ReservationXML> reservations = new LinkedList<ReservationXML>();
    }
    private class ReservationXML {
        protected ReservationXML(Timestamp time) { this.time = time; }
        protected Timestamp time;
        protected Timestamp duration;
        protected String refResource;
    }
}
